/*
 * jni.c
 *
 * Fake Java Native Interface, providing JVM and Env objects.
 *
 * Copyright (C) 2021 Andy Nguyen
 * Copyright (C) 2021 Rinnegatamante
 * Copyright (C) 2022 Volodymyr Atamanenko
 *
 * This software may be modified and distributed under the terms
 * of the MIT license. See the LICENSE file for details.
 */

#include <string.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#include <psp2/apputil.h>
#include <psp2/system_param.h>
#include <psp2/kernel/clib.h>

#include "jni.h"
#include "utils.h"
#include "dialog.h"
#include "patch/patch.h"

char fake_vm[0x1000];
char fake_env[0x1000];

enum MethodIDs
{
	UNKNOWN = 0,
	GET_CONTEXT,
	OPEN_APK_EXPANSION_INPUT_STREAM,
	GET_APKX_FD,
	AD_CHECK,
	GET_APKX_READ_APTH,
	GET_APKX_PATH,
	APKX_SET_FILE,
	GET_APKX_FILE_LEN,
	GET_APKX_FILE_OFFSET,
	NATIVE_GET_SD_PATH,
	NATIVE_GET_INTER_PATH,
	IS_EXISTS,
	GET_FILE_LAST_WRITE_TIME,
	GET_LOCAL_ID,
	NATIVE_GET_GAME_SAVE_PATH,
	NATIVE_GET_TARGET_PATH,
	GAME_INSTALL,
	SET_GAME_MODE,
	CALL_WEB_BROWSER,
	CALL_CHARTBOOST_MORE_APP,
	GET_DPI,
	N_METHOD_IDS
} MethodIDs;

typedef struct
{
	char *name;
	enum MethodIDs id;
} NameToMethodID;

static NameToMethodID name_to_method_ids[] = {
	{"getContext", GET_CONTEXT},
	{"openAPKExpansionInputStream", OPEN_APK_EXPANSION_INPUT_STREAM},
	{"getAPKXfd", GET_APKX_FD},
	{"ADCheck", AD_CHECK},
	{"GetAPKXreadpath", GET_APKX_READ_APTH},
	{"GetAPKXpath", GET_APKX_PATH},
	{"APKX_SetFile", APKX_SET_FILE},
	{"GetAPKXFileLen", GET_APKX_FILE_LEN},
	{"GetAPKXFileOffset", GET_APKX_FILE_OFFSET},
	{"nativeGetSDPath", NATIVE_GET_SD_PATH},
	{"nativeGetInterPath", NATIVE_GET_INTER_PATH},
	{"isExists", IS_EXISTS},
	{"GetFileLastWriteTime", GET_FILE_LAST_WRITE_TIME},
	{"GetLocalID", GET_LOCAL_ID},
	{"nativeGetGAMESAVEPath", NATIVE_GET_GAME_SAVE_PATH},
	{"nativeGetTargetPath", NATIVE_GET_TARGET_PATH},
	{"GameInstall", GAME_INSTALL},
	{"SetGameMode", SET_GAME_MODE},
	{"CallWebBrowser", CALL_WEB_BROWSER},
	{"CallChartboostMoreApp", CALL_CHARTBOOST_MORE_APP},
	{"GetDPI", GET_DPI},
};

// VM functions:

int GetEnv(void *vm, void **env, int r2)
{
	debugPrintf("GetEnv()\n");
	*env = fake_env;
	return 0;
}

int RegisterNatives(void *e, void *c, void *methods, int method_count)
{
	debugPrintf("RegisterNatives( for %i methods )\n", method_count);
	return 0;
}

int PushLocalFrame(void *env, int capacity)
{
	debugPrintf("PushLocalFrame called\n");
	return 0;
}

int GetMethodID(void *env, void *class, const char *name, const char *sig)
{
	debugPrintf("GetStaticMethodID(%s)\n", name);
	for (int i = 0; i < N_METHOD_IDS; i++)
	{
		if (strcmp(name, name_to_method_ids[i].name) == 0)
		{
			return name_to_method_ids[i].id;
		}
	}

	debugPrintf("Attempted to get an unknown method ID with name %s\n", name);
	return 0;
}

int GetStaticMethodID(void *env, void *class, const char *name, const char *sig)
{
	debugPrintf("GetStaticMethodID(%s)\n", name);
	for (int i = 0; i < N_METHOD_IDS; i++)
	{
		if (strcmp(name, name_to_method_ids[i].name) == 0)
		{
			return name_to_method_ids[i].id;
		}
	}

	debugPrintf("Attempted to get an unknown static method ID with name %s\n", name);
	return 0;
}

typedef struct
{
	char *module_name;
	char *method_name;
	int argc;
	double *double_array;
	void *object_array;
} ext_func;

void *CallStaticObjectMethodV(void *env, void *obj, int methodID, uintptr_t *args)
{
	if (methodID > 0 && methodID < N_METHOD_IDS)
		debugPrintf("CallStaticObjectMethodV(%s / %d)\n", name_to_method_ids[methodID - 1].name, methodID);

	switch (methodID)
	{
	case NATIVE_GET_TARGET_PATH:
		return DATA_PATH;
	
	case NATIVE_GET_INTER_PATH:
		return DATA_PATH;
		
	case NATIVE_GET_GAME_SAVE_PATH:
		return GAME_SAVE_PATH;

	case NATIVE_GET_SD_PATH:
		// return "ux0:";
		return DATA_PATH;

	default:
		return NULL;
	}
}

int CallStaticIntMethodV(void *env, void *obj, int methodID, uintptr_t *args)
{
	if (methodID > 0 && methodID < N_METHOD_IDS)
		debugPrintf("CallStaticIntMethodV(%s / %d)\n", name_to_method_ids[methodID - 1].name, methodID);

	switch (methodID)
	{
	case GET_LOCAL_ID:
		return Android_JNI_GetLocalID();

	case GAME_INSTALL:
		return 1;

	case AD_CHECK:
		return 0;

	default:
		return 0;
	}
}

double CallStaticDoubleMethodV(void *env, void *obj, int methodID, uintptr_t *args)
{
	if (methodID > 0 && methodID < N_METHOD_IDS)
		debugPrintf("CallStaticDoubleMethodV(%s / %d)\n", name_to_method_ids[methodID - 1].name, methodID);

	switch (methodID)
	{
	default:
		return 0;
	}
}

void CallStaticVoidMethodV(void *env, void *obj, int methodID, uintptr_t *args)
{
	if (methodID > 0 && methodID < N_METHOD_IDS && methodID != SET_GAME_MODE)
		debugPrintf("CallStaticVoidMethodV(%s / %d)\n", name_to_method_ids[methodID - 1].name, methodID);

	switch (methodID)
	{
	default:
		break;
	}
}

int CallStaticBooleanMethodV(void *env, void *obj, int methodID, uintptr_t *args)
{
	if (methodID > 0 && methodID < N_METHOD_IDS)
		debugPrintf("CallStaticBooleanMethodV(%s / %d)\n", name_to_method_ids[methodID - 1].name, methodID);

	switch (methodID)
	{
	case IS_EXISTS:
	{
		char *path = *(char **)args;
		int ret = file_exists(path);
		debugPrintf("path: %s, IsExists: %d\n", path, ret);
		return ret;
	}
	default:
		return 0;
	}
}

int CallStaticByteMethod(void *env, void *obj, int methodID, uintptr_t *args)
{
	if (methodID > 0 && methodID < N_METHOD_IDS)
		debugPrintf("CallStaticByteMethod(%s / %d)\n", name_to_method_ids[methodID - 1].name, methodID);

	switch (methodID)
	{
	default:
		return 0;
	}
	return 0;
}

uint64_t CallLongMethodV(void *env, void *obj, int methodID, uintptr_t *args)
{
	if (methodID > 0 && methodID < N_METHOD_IDS)
		debugPrintf("CallLongMethodV(%s / %d)\n", name_to_method_ids[methodID - 1].name, methodID);

	switch (methodID)
	{
	default:
		return 0;
	}
	return 0;
}

enum ClassIDs
{
	STRING
};

int FindClass(void *env, const char *name)
{
	debugPrintf("FindClass: %s\n", name);

	if (!strcmp(name, "java/lang/String"))
	{
		return STRING;
	}
	return 0x41414141;
}

void *NewGlobalRef(void *env, char *str)
{
	return (void *)0x42424242;
}

void *NewWeakGlobalRef(void *env, char *str)
{
	return (void *)0x42424242;
}

void DeleteGlobalRef(void *env, char *str)
{
}

void *NewObjectV(void *env, void *clazz, int methodID, uintptr_t args)
{
	return (void *)0x43434343;
}

void *GetObjectClass(void *env, void *obj)
{
	return (void *)0x44444444;
}

char *NewString(void *env, char *bytes)
{
	return bytes;
}

char *NewStringUTF(void *env, char *bytes)
{
	return bytes;
}

char *GetStringUTFChars(void *env, char *string, int *isCopy)
{
	if (isCopy)
		*isCopy = 0;
	return string;
}

int GetJavaVM(void *env, void **vm)
{
	*vm = fake_vm;
	return 0;
}

int GetFieldID(void *env, void *clazz, const char *name, const char *sig)
{
	return 0;
}

enum
{
	UNKNOWN2,
	MANUFACTURER
};

int GetStaticFieldID(void *env, void *clazz, const char *name, const char *sig)
{
	if (!strcmp("MANUFACTURER", name))
		return MANUFACTURER;
	return 0;
}

void *GetStaticObjectField(void *env, void *clazz, int fieldID)
{
	static char *r = NULL;
	switch (fieldID)
	{
	case MANUFACTURER:
		if (!r)
			r = malloc(0x100);
		strcpy(r, "Rinnegatamante");
		return r;
	default:
		debugPrintf("GetStaticObjectField(%s / %d)\n", clazz, fieldID);

		return NULL;
	}
}

int GetBooleanField(void *env, void *obj, int fieldID)
{
	return 0;
}

void *CallObjectMethodV(void *env, void *obj, int methodID, uintptr_t *args)
{
	if (methodID > 0 && methodID < N_METHOD_IDS)
		debugPrintf("CallObjectMethodV(%s / %d)\n", name_to_method_ids[methodID - 1].name, methodID);

	return NULL;
}

int CallBooleanMethodV(void *env, void *obj, int methodID, uintptr_t *args)
{
	if (methodID > 0 && methodID < N_METHOD_IDS)
		debugPrintf("CallBooleanMethodV(%s / %d)\n", name_to_method_ids[methodID - 1].name, methodID);

	switch (methodID)
	{
	default:
		return 0;
	}
}

double CallDoubleMethodV(void *env, void *obj, int methodID, uintptr_t *args)
{
	if (methodID > 0 && methodID < N_METHOD_IDS)
		debugPrintf("CallDoubleMethodV(%s / %d)\n", name_to_method_ids[methodID - 1].name, methodID);

	switch (methodID)
	{
	default:
		break;
	}
	return 0.0;
}

void CallVoidMethodV(void *env, void *obj, int methodID, uintptr_t *args)
{
	if (methodID > 0 && methodID < N_METHOD_IDS)
		debugPrintf("CallVoidMethodV(%s / %d)\n", name_to_method_ids[methodID - 1].name, methodID);

	switch (methodID)
	{
	default:
		break;
	}
}

void *NewIntArray(void *env, int size)
{
	return malloc(sizeof(int) * size);
}

void *NewObjectArray(void *env, int size, int clazz, void *elements)
{
	void *r = malloc(size);
	if (elements)
	{
		sceClibMemcpy(r, elements, size);
	}
	return r;
}

void *NewDoubleArray(void *env, int size)
{
	return malloc(sizeof(double) * size);
}

int GetArrayLength(void *env, void *array)
{
	return 0;
}

void SetIntArrayRegion(void *env, int *array, int start, int len, int *buf)
{
	sceClibMemcpy(&array[start], buf, sizeof(int) * len);
}

void SetDoubleArrayRegion(void *env, double *array, int start, int len, double *buf)
{
	sceClibMemcpy(&array[start], buf, sizeof(double) * len);
}

void SetObjectArrayElement(void *env, void *array, int index, void *val)
{
	if (array)
	{
		char *dst = (char *)array;
		strcpy(&dst[index], val);
	}
}

void GetByteArrayRegion(void *env, void *array, int start, int len, void *buf)
{
	int8_t *src = (int8_t *)array;
	sceClibMemcpy(buf, &src[start], len);
}

int GetIntField(void *env, void *obj, int fieldID)
{
	debugPrintf("GetIntField(%p)\n", fieldID);

	return 0;
}

void init_jni()
{
	memset(fake_vm, 'A', sizeof(fake_vm));
	*(uintptr_t *)(fake_vm + 0x00) = (uintptr_t)fake_vm; // just point to itself...
	*(uintptr_t *)(fake_vm + 0x10) = (uintptr_t)GetEnv;
	*(uintptr_t *)(fake_vm + 0x14) = (uintptr_t)ret0;
	*(uintptr_t *)(fake_vm + 0x18) = (uintptr_t)GetEnv;

	memset(fake_env, 'A', sizeof(fake_env));
	*(uintptr_t *)(fake_env + 0x00) = (uintptr_t)fake_env; // just point to itself...
	*(uintptr_t *)(fake_env + 0x18) = (uintptr_t)FindClass;
	*(uintptr_t *)(fake_env + 0x4C) = (uintptr_t)PushLocalFrame;
	*(uintptr_t *)(fake_env + 0x54) = (uintptr_t)NewGlobalRef;
	*(uintptr_t *)(fake_env + 0x58) = (uintptr_t)DeleteGlobalRef;
	*(uintptr_t *)(fake_env + 0x5C) = (uintptr_t)ret0; // DeleteLocalRef
	*(uintptr_t *)(fake_env + 0x68) = (uintptr_t)ret0; // EnsureLocalCapacity
	*(uintptr_t *)(fake_env + 0x74) = (uintptr_t)NewObjectV;
	*(uintptr_t *)(fake_env + 0x7C) = (uintptr_t)GetObjectClass;
	*(uintptr_t *)(fake_env + 0x80) = (uintptr_t)ret1; // IsInstanceOf
	*(uintptr_t *)(fake_env + 0x84) = (uintptr_t)GetMethodID;
	*(uintptr_t *)(fake_env + 0x8C) = (uintptr_t)CallObjectMethodV;
	*(uintptr_t *)(fake_env + 0x98) = (uintptr_t)CallBooleanMethodV;
	*(uintptr_t *)(fake_env + 0xD4) = (uintptr_t)CallLongMethodV;
	*(uintptr_t *)(fake_env + 0xEC) = (uintptr_t)CallDoubleMethodV;
	*(uintptr_t *)(fake_env + 0xF8) = (uintptr_t)CallVoidMethodV;
	*(uintptr_t *)(fake_env + 0x178) = (uintptr_t)GetFieldID;
	*(uintptr_t *)(fake_env + 0x17C) = (uintptr_t)GetBooleanField;
	*(uintptr_t *)(fake_env + 0x190) = (uintptr_t)GetIntField;
	*(uintptr_t *)(fake_env + 0x1C4) = (uintptr_t)GetStaticMethodID;
	*(uintptr_t *)(fake_env + 0x1CC) = (uintptr_t)CallStaticObjectMethodV;
	*(uintptr_t *)(fake_env + 0x1D8) = (uintptr_t)CallStaticBooleanMethodV;
	*(uintptr_t *)(fake_env + 0x1E0) = (uintptr_t)CallStaticByteMethod;
	*(uintptr_t *)(fake_env + 0x208) = (uintptr_t)CallStaticIntMethodV;
	*(uintptr_t *)(fake_env + 0x22C) = (uintptr_t)CallStaticDoubleMethodV;
	*(uintptr_t *)(fake_env + 0x238) = (uintptr_t)CallStaticVoidMethodV;
	*(uintptr_t *)(fake_env + 0x240) = (uintptr_t)GetStaticFieldID;
	*(uintptr_t *)(fake_env + 0x244) = (uintptr_t)GetStaticObjectField;
	*(uintptr_t *)(fake_env + 0x28C) = (uintptr_t)NewString;
	*(uintptr_t *)(fake_env + 0x29C) = (uintptr_t)NewStringUTF;
	*(uintptr_t *)(fake_env + 0x2A4) = (uintptr_t)GetStringUTFChars;
	*(uintptr_t *)(fake_env + 0x2A8) = (uintptr_t)ret0; // ReleaseStringUTFChars
	*(uintptr_t *)(fake_env + 0x2AC) = (uintptr_t)GetArrayLength;
	*(uintptr_t *)(fake_env + 0x2B0) = (uintptr_t)NewObjectArray;
	*(uintptr_t *)(fake_env + 0x2B8) = (uintptr_t)SetObjectArrayElement;
	*(uintptr_t *)(fake_env + 0x2CC) = (uintptr_t)NewIntArray;
	*(uintptr_t *)(fake_env + 0x2D8) = (uintptr_t)NewDoubleArray;
	*(uintptr_t *)(fake_env + 0x320) = (uintptr_t)GetByteArrayRegion;
	*(uintptr_t *)(fake_env + 0x34C) = (uintptr_t)SetIntArrayRegion;
	*(uintptr_t *)(fake_env + 0x358) = (uintptr_t)SetDoubleArrayRegion;
	*(uintptr_t *)(fake_env + 0x36C) = (uintptr_t)GetJavaVM;
	*(uintptr_t *)(fake_env + 0x394) = (uintptr_t)NewWeakGlobalRef;
}
